package com.xingchi.tornado.storage.core;

import com.xingchi.tornado.basic.ErrorCode;
import com.xingchi.tornado.exception.BizException;
import com.xingchi.tornado.storage.core.exception.FileUploadException;
import com.xingchi.tornado.storage.core.model.dto.FileChunkMergeDTO;
import com.xingchi.tornado.storage.core.model.dto.FileChunkUploadDTO;
import com.xingchi.tornado.storage.core.model.vo.FileChunkUploadVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;

/**
 * 文件存储服务模块
 *
 * @author xingchi
 * @date 2024/4/8 21:36
 */
@Slf4j
public abstract class AbstractFileStorageService implements FileStorageService {


    protected static final byte[] EMPTY_BYTES = new byte[0];

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("/yyyy/MMdd/HHmm/");

    protected String getPath(String fileName) {
        return "/storage" + DATE_TIME_FORMATTER.format(LocalDateTime.now()) + RandomStringUtils.randomAlphabetic(8) + "." + FilenameUtils.getExtension(fileName);
    }

    protected String getPath(String start, String fileName) {
        return FilenameUtils.separatorsToUnix(FilenameUtils.normalize((StringUtils.equalsAny(start, "/", "\\") ? "" : start) + fileName));
    }

    @Override
    public String uploadFile(String filePath) throws FileNotFoundException {
        if (StringUtils.isBlank(filePath) || !new File(filePath).exists()) {
            throw new FileNotFoundException(String.format("文件不存在[%s]", filePath));
        }
        try {
            return uploadFile(Files.newInputStream(Paths.get(filePath)), FilenameUtils.getName(filePath));
        } catch (IOException e) {
            log.error("文件上传失败，原因：{}", e.getMessage(), e);
            throw BizException.of(ErrorCode.FILE_UPLOAD_FAILED, e.getMessage());
        }
    }

    @Override
    public String uploadFile(Path filePath) throws FileNotFoundException {
        if (filePath == null || !Files.exists(filePath)) {
            throw new FileNotFoundException(String.format("文件不存在[%s]", filePath));
        }

        try {
            return uploadFile(Files.newInputStream(filePath), FilenameUtils.getName(filePath.toFile().getName()));
        } catch (IOException e) {
            log.error("文件上传失败，原因：{}", e.getMessage(), e);
            throw BizException.of(ErrorCode.FILE_UPLOAD_FAILED, e.getMessage());
        }
    }

    @Override
    public String uploadFile(byte[] bytes, String fileName) {
        return uploadFile(new ByteArrayInputStream(bytes), fileName);
    }

    /**
     * 获取分片存储目录
     */
    @Override
    public String getChunkPath(String identifier) {
        return "/chunks/" + identifier + "/";
    }

    /**
     * 获取分片文件名
     */
    protected String getChunkFileName(FileChunkUploadDTO info) {
        return info.getChunkNumber() + ".chunk";
    }

    /**
     * 检查分片是否已上传
     */
    protected abstract boolean isChunkExists(String chunkPath);

    /**
     * 上传分片
     */
    protected abstract void storeChunk(FileChunkUploadDTO info, String chunkPath);

    /**
     * 合并分片
     */
    protected abstract String combineChunks(FileChunkMergeDTO info, String targetPath);

    @Override
    public FileChunkUploadVO checkChunkUpload(FileChunkUploadDTO info) {
        String chunkPath = getChunkPath(info.getSha256());
        Set<Integer> uploaded = new HashSet<>();

        // 检查已上传的分片
        for (int i = 1; i <= info.getTotalChunks(); i++) {
            String chunk = chunkPath + i + ".chunk";
            if (isChunkExists(chunk)) {
                uploaded.add(i);
            }
        }

        boolean isFinished = uploaded.size() == info.getTotalChunks();

        return FileChunkUploadVO.builder()
                .skipUpload(uploaded.contains(info.getChunkNumber()))
                .uploaded(uploaded)
                .isFinished(isFinished)
                .build();
    }

    @Override
    public String uploadChunk(FileChunkUploadDTO info) {
        String chunkPath = getChunkPath(info.getSha256()) + getChunkFileName(info);
        storeChunk(info, chunkPath);
        return chunkPath;
    }

    @Override
    public String mergeChunks(FileChunkMergeDTO info, Supplier<FileChunkUploadVO> supplier) {
        FileChunkUploadVO check = supplier.get();
        if (!check.isFinished()) {
            throw new FileUploadException("分片未完全上传");
        }

        String targetPath = getPath(info.getFilename());
        return combineChunks(info, targetPath);
    }
}
